#include <phpcpp.h>
#include <iostream>

void myFunction() {
    Php::out << "hello world" << std::endl;
}

void swap(Php::Parameters& params) {
    Php::Value temp = params[0];
    params[0] = params[1];
    params[1] = temp;
}

void example_function(Php::Parameters& params) {
    // first paramter is an array
    Php::Value array = params[0];

    // call the php array_key() function to get paramter keys
    std::vector<std::string> keys = Php::array_keys(array);

    // loop through the keys
    for (auto &key : keys) {
        // output keys
        Php::out << "key: " << key << std::endl;
    }

    // call a function from user space
    Php::Value data = Php::call("some_function", "some_parameters");

    // create an object (this will also call __construct())
    Php::Object time("DateTime", "now");

    // call a method on the datetime object
    Php::out << time.call("format", "Y-m-d H:i:s") << std::endl;

    // second parameter is a callback function
    Php::Value callback = params[1];

    // call the callback function
    callback("some", "parameter");

    // in PHP it is possible to create an array with two parameters, the first
    // parameter being an object, and the second paramter should be the name
    // of the method , we can do that in php-cpp too
    Php::Array time_format({time, "format"});

    // call the method that is stored in the array
    Php::out << time_format("Y-m-d H:i:s") << std::endl;

}

Php::Value other_function(Php::Parameters& params) {
    // make sure the function was really called with at least one paramter
    if (params.size() == 0) return nullptr;

    // this function is called from PHP user space, and it is called
    // with a anonymous function as its first parameter
    Php::Value func = params[0];

    // the PHP::Value class has implemented the operator() , which allows
    // us to use the object just as if it is a real function
    Php::Value result = func(3, 4);

    Php::out << "3 op 4 is : " << result << std::endl;
    return result;
}

Php::Value multiply_by_two_2(Php::Parameters& params) {
    if (params.empty()) return nullptr;

    Php::Value result = params[0] * 2;
    return result;
}

void run_test() {
    // create the anonymous function
    Php::Function multiply_by_two([](Php::Parameters& params)->Php::Value {
        // make sue the function was really called with at least one paramter
        if (params.empty()) return nullptr;

        // one paramter is passed to the function
        Php::Value param = params[0];

        // multiple the paramter by two
        Php::Value result = param * 2;
        params[0] = result;
        return result;
    });

    // the function now is callable
    Php::Value four = multiply_by_two(2);
    Php::out << "four: " << four << std::endl; // 这里没法打印出来，lambda的返回值为NULL，不知道为啥

    // a Php::Function object is a derived Php::Value, and its value can
    // also be stored in normal Php::Value object, it will then still
    // be a callback function then
    Php::Value value = multiply_by_two;

    // the value object now also holds the function
    Php::Value six = value(3);

    // create an array
    Php::Value array;
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;

    // call the user-space function
    Php::Value result = Php::call("my_array_map", array, multiply_by_two);
}


/* 
* Counter class that can be used for counting
*/
class Counter : public Php::Base {
private:
    /*
    * The initial Value
    */
   int value_ = 0;

public:
    /*
    * c++ constructor and destructor
    */
   Counter(int value) : value_(value) {}
   virtual ~Counter() = default;

   // php __constructor
   void __construct(Php::Parameters& params) {
       if (!params.empty()) value_ = params[0];
   }

   /*
   * Update methods to increment or decrement the counter
   * @return int 
   */
  Php::Value increment(Php::Parameters& params) { 
      if (params.empty()) {
          value_ += 1;
      } else {
          value_ += params[0];
      }
      return value_;
  }
  Php::Value decrement(Php::Parameters& params) { 
      if (params.empty()) {
          value_ -= 1;
      } else {
          value_ -= params[0];
      }
      return value_ ;
  }

  Php::Value value() { return value_;}
};

// function to create a new timer
Php::Value createTimer(Php::Parameters& params) {
    if (params.empty()) return nullptr;
    return Php::Object("Counter", new Counter(params[0]));
}

/*
* Regular function
*/
void regularFunction(Php::Parameters& params) {
    Php::out << "regularFunction" << std::endl;
}

/*
* a very simple class that will not be exported to php
* not herit from php::base
*/
class PrivateClass {
    public:
        /*
        * c++ constructor and destructor
        */
       PrivateClass() = default;
       ~PrivateClass() = default;

       // static method
       static void staticMethod(Php::Parameters& params) {
           Php::out << "private static method" << std::endl;
       }
};

// a very simple class that will be exported to php
class PublicClass : public Php::Base {
    public:
        // constructor and destructor
        PublicClass() = default;
        virtual ~PublicClass() = default;

        // static method
        static void staticMethod(Php::Parameters& params) {
            Php::out << "public static method" << std::endl;
        }

};

class User: public Php::Base {
    private:
        // name fo the user
        std::string name_;

        // email address of the user
        std::string email_;

    public:
        // c++ constructor and c++ destructor
        User() = default;
        virtual ~User() = default;

        // get access to property
        Php::Value __get(const Php::Value& name) {
            // check if the property name is supported
            if (name == "name") return name_;
            if (name == "email") return email_;

            // property not supported, fall back on default
            return Php::Base::__get(name);
        }

        // overwrite a property
        void __set(const Php::Value& name, const Php::Value& value) {
            // check the property name
            if (name == "name")
            {
                // store member
                name_ = value.stringValue();
            }
            else if (name == "email") {
                // store the email in a string
                std::string email = value;

                // must have a '@' character in it
                if (email.find('@') == std::string::npos) {
                    // email address in invalid, throw exception
                    throw Php::Exception("Invalid email address");
                }

                // store the member
                email_ = email;
            } else {
                // call back default 
                Php::Base::__set(name, value);
            }
        } 

        // check if property is set
        bool __isset(const Php::Value& name) {
            // true for name and email address
            if (name == "name" || name == "email") return true;

            // fallback to default
            return Php::Base::__isset(name);
        }

        // remove a property
        void __unset(const Php::Value& name) {
            // name and email can not be unset
            if (name == "name" || name == "email") {
                throw Php::Exception("name and email address can not be removed");
            }
            
            // fall back to default
            Php::Base::__unset(name);
        }

        // regular method
        Php::Value regular(Php::Parameters& params) {
            return "this is a regular method";
        }

        // overriden __call() method to accept all method calls
        const char* __call(const char* name, Php::Parameters& params) {
            // the return value
            std::string retval = std::string("__call ") + name;

            // lpp through the parameters
            for (auto &param: params) {
                // append paramter string value to return value
                retval += " " + param.stringValue();
            }
            //Php::out << retval << std::endl;

            // done
            // return null here, why?
            return retval.c_str();
        }

        // overriden __callStatic() method to accept all static method calls
        static Php::Value __callStatic(const char* name, Php::Parameters& params) {
            // the return value
            std::string retval = std::string("__callStatic ") + name;

            // loop through the parameters
            for (auto &param: params) {
                // append parameter string value to return value
                retval += " " + param.stringValue();
            }

            // done 
            return retval;
        }

        // overriden __invoke() method so that objects can be called directly
        Php::Value __invoke(Php::Parameters& params) {
            // the return value
            std::string retval = "invoke ";

            // loop through the parameters
            for (auto &param: params) {
                // append parameter string value to return value
                retval += " " + param.stringValue();
            }

            // done
            return retval;
        }
};

class CastObject : public Php::Base {
public:
    // c++ constructor and destructor
    CastObject() = default;
    virtual ~CastObject() = default;

    // cast to string
    const char* __toString() {
        return "abcd";
    }

    // cast to integer
    long __toInteger() {
        return 1234;
    }

    // cast to float
    double __toFloat() {
        return 88.88;
    }

    // cast to boolean
    bool __toBool() {
        return true;
    }
};

/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
    
    /**
     *  Function that is called by PHP right after the PHP process
     *  has started, and that returns an address of an internal PHP
     *  strucure with all the details and features of your extension
     *
     *  @return void*   a pointer to an address that is understood by PHP
     */
    PHPCPP_EXPORT void *get_module() 
    {
        // static(!) Php::Extension object that should stay in memory
        // for the entire duration of the process (that's why it's static)
        static Php::Extension extension("lijunextension", "1.0");
        
        // @todo    add your own functions, classes, namespaces to the extension
        extension.add<myFunction>("myFunction");

        extension.add<swap>("swap", {
            Php::ByRef("a", Php::Type::Numeric),
            Php::ByRef("b", Php::Type::Numeric),
        });

        extension.add<example_function>("example_function");

        extension.add<other_function>("other_function");

        extension.add<run_test>("run_test");


        // description of the class so that PHP knows which methods are accessible
        Php::Class<Counter> counter("Counter");
        counter.method<&Counter::__construct>("__construct", Php::Private);
        counter.method<&Counter::increment>("increment", {
            Php::ByVal("change", Php::Type::Numeric, false)
        });
        counter.method<&Counter::decrement>("decrement", {
            Php::ByVal("change", Php::Type::Numeric, false)
        });
        counter.method<&Counter::value>("value", {});

        // add the class to the extension
        extension.add(std::move(counter));


        Php::Class<PublicClass> publicClass("PublicClass");
        publicClass.method<&PrivateClass::staticMethod>("privateStaticMethod");
        publicClass.method<&PublicClass::staticMethod>("publicStaticMethod");
        publicClass.method<regularFunction>("regularFunction");
        extension.add(std::move(publicClass));

        extension.add<createTimer>("createTimer");

        // description of the class so that PHP knows
        // which methods are accessible
        Php::Class<User> user("User");
        user.method<&User::regular>("regular");
        extension.add(std::move(user));

        Php::Class<CastObject> castobject("CastObject");
        extension.add(std::move(castobject));
    
        // return the extension
        return extension;
    }
}
